<?php

/**
 * Core class
 *
 * @author Tomasz Slominski <tomasz@yzeo.fr>
 * @date 2007-10-31
 * @version 2.2
 * @package yzFramework
 */
class yzCore {

	/**
	 * Class properties
	 *
	 * @var array
	 */
	protected $aProperties = array();

	/**
	 * Class objects
	 *
	 * @var array
	 */
	protected $aObjects = array();

	/**
	 * Constructor
	 *
	 * Gets all params and adds to objects / properties
	 * If only one argument is passed and its array, its assumed as the array of properties (you can't pass objects in this)
	 *
	 */
	public function __construct(){

		$aArgs = func_get_args();

		$nArgs = func_num_args();

		$sGetPropertiesPattern = $this->convertCCToName(get_class($this)).'*';

		if ($nArgs==1 && is_array($aArgs[0])){

            $this->importProperties($aArgs[0]);

		} else if ($nArgs==1 && is_object($aArgs[0])){

			if ($aArgs[0] instanceof yzCore){

				$aObjects = $aArgs[0]->getObjects();

				foreach ((array)$aObjects as $sName=>$oObject){

				    $this->setObject($sName,$oObject);

			     }  // foreach

				$this->importProperties($aArgs[0]->getProperties($sGetPropertiesPattern));

			} // if

            $this->setObject('__creator',$aArgs[0]);

		} else {


			foreach ($aArgs as $mKey=>$mArgument){

				if (is_object($mArgument)){

					$this->setObject(is_string($mKey) ? $mKey : get_class($mArgument) ,$mArgument);

					if ($mArgument instanceof yzCore){

						$this->importProperties($mArgument->getProperties($sGetPropertiesPattern));

					} // if

					unset($aArgs[$mKey]);

				} // if

			} // foreach

            $this->importProperties($aArgs);

		} // if

		if ($this->getProperty('runtime.no_init')!==true && method_exists($this,'init')){

			$this->init();

		} // if

	} // function __construct

	/**
	 * Automagic getter
	 *
	 * Gets object or property; In case of name conflict, objects have priority
	 *
	 * @param string $sName Object or property name
	 * @return object|mixed Object or property value
	 */
	public function __get($sName){

		if ($this->isSetObject($sName)){

			return $this->getObject($sName);

		} else if ($this->isSetProperty($sName=$this->convertCCToName($sName))){

			return $this->getProperty($sName);

		} else {

			return null;

		} // if

	} // function __get

	/**
	 * automagic __set
	 *
	 * Sets object or property
	 *
	 * @param string $sName
	 * @param object|mixed $mValue Property or object
	 * @return mixed property or object
	 */
	public function __set($sName, $mValue){

		if (is_object($mValue)){

			return $this->setObject($sName, $mValue);

		} else {

			return $this->setProperty($this->convertCCToName($sName), $mValue);

		} // if

	} // function __set

	/**
	 * Automagic __unset, unsets property and/or object with given name
	 *
	 * @param string $sName Name of object or property
	 * @return int 0 if no item unset, 1 if property unset, 2 if object unset, 3 if both unset
	 */
	public function __unset($sName){

		$nCount = 0;

		if ($this->unsetProperty($this->convertCCToName($sName))) $nCount = $nCount + 1;

		if ($this->unsetObject($sName))  $nCount = $nCount + 2;

		return $nCount;

	} // function unset()

    /**
     * Automagic __isset, checks if is set property and/or object with given name
     *
     * @param string $sName Name of object or property
     * @return int 0 if no item is set, 1 if property is set, 2 if object is set, 3 if both are set
     */
	public function __isset($sName){

        $nCount = 0;

        if ($this->isSetProperty($this->convertCCToName($sName))) $nCount = $nCount + 1;

        if ($this->isSetObject($sName))  $nCount = $nCount + 2;

        return $nCount;

	} // function __isset()

	/**
	 * automagic __call
	 *
	 * @param string $sName Method name
	 * @param array $aParams Method parameters
	 * @throws Exception
	 */
	public function __call($sName, $aParams){

		foreach ($this->aObjects as $oObject){

			if (method_exists($oObject,$sName)){

				return call_user_func_array(array($oObject,$sName), $aParams);

			} // if

		} // foreach

		throw new Exception(get_class($this).' !< '.$sName);

	} // function __call()

	/**
	 * Returns default string output of class
	 *
	 * @return string
	 */
	public function __toString(){

		return strval($this->getProperty('_result'));

	} // function __toString()

	/**
     * Gets object
     *
     * @param string $sName Object name
     * @return mixed|null Object or null if property not set
     */
	public function getObject($sName){

        if (isset($this->aObjects[$sName])){

            return $this->aObjects[$sName];

        } else {

            return null;

        } // if

	} // if

	/**
	 * Sets object
	 *
	 * Allowed modes:
	 *
	 * 1/ string name, object object - sets object with given name
	 * 2/ string name, string class_name - creates object of given class
	 * 3/ object object - sets object with name of the class
	 * 4/ string name - returns existing object
	 * 5/ string class_name - creates object of given class with name of the class
	 *
	 * Prefered usage is the premier (straight) mode
	 * Third mode always overwrite existing object with thesame name
	 *
	 * @param string|object $sName name of object or name of class or object
	 * @param object|string|null|bool $mValue object or class name or null ; gets place of $bPassThis in 5th mode
	 * @param bool $bPassThis If true, passing $this to constructor (in applicable modes 2 & 5)
	 * @return object|null
	 *
	 * @todo further clarification of existing modes
	 *
	 */
	public function setObject($sName, $mValue = null, $bPassThis = true){

        if ((is_object($sName) && $sName===$this) || (is_object($mValue) && $mValue===$this)){

        	return $this;

        } else if (is_object($sName)){

            $this->aObjects[get_class($sName)] = &$sName;

        } else if ($this->isSetObject($sName)){

            $this->getObject($sName);

        } else if (is_string($sName) && is_object($mValue)){

            $this->aObjects[$sName] =&$mValue;

        } else if (is_string($sName) && class_exists($mValue)){

        	if ($bPassThis===true && is_subclass_of($mValue,'yzCore')) {

        		$this->aObjects[$sName] = new $mValue($this);

        	} else {

        		$this->aObjects[$sName] = new $mValue();

        	} // if

        } else if (class_exists($sName)){

        	if (is_bool($mValue)){

        		$bPassThis = $mValue;

        	} // if

         	if ($bPassThis===true  && is_subclass_of($sName,'yzCore')) {

           		$this->aObjects[$sName] = new $sName($this);

         	} else {

           		$this->aObjects[$sName] = new $sName();

         	} // if


           if ($this->aObjects[$sName] instanceof yzCore){

                $this->aObjects[$sName]->importProperties($this->getProperties($this->convertCCToName(get_class($this->aObjects[$sName])).'*'));

            } // if

         	return $this->aObjects[$sName];

        } else {

            return null;

        } // if

	} // function setObject

	/**
	 * Checks wheether object is set
	 *
	 * @param string $sName Object name
	 * @return bool true if object is set, false otherwise
	 */
	public function isSetObject($sName){

		return isset($this->aObjects[$sName]);

	} // functon isSetObject()

	/**
     * Unsets object
     *
     * @param string $sName Object name
     * @return bool True if object existed and was unset, false otherwise
     *
     * @todo Exclusive mode with checking if object is not referenced in other place
     */
	public function unsetObject($mObject){

		if (is_object($mObject)){

			$bObjectUnset = false;

			foreach ($this->aObjects as $sKey=>$mCurrentObject){

				if ($mObject === $mCurrentObject){

					unset($this->aObjects[$sKey], $mCurrentObject);

					$bObjectUnset = true;

				} // if

			} // foreach

			return $bObjectUnset;

		} else {

		  if (isset($this->aObjects[$mObject])){

			    unset($this->aObjects[$mObject]);

		       	return true;

		      } else {

		      	return false;

	       	} // if

		} // if

	} // function unsetObject()

	/**
	 * Gets value of property
	 *
	 * @param string $sName Property name
	 * @return mixed|null Property value or null if property not set
	 */
	public function getProperty($sName){

        if (isset($this->aProperties[$sName])){

            return $this->aProperties[$sName];

        } else {

            return null;

        } // if

    } // function getProperty()

    /**
     * Sets property
     *
     * @param string $sName Property name
     * @param mixed $mValue Property value
     * @return mixed property value
     */
	public function setProperty($sName, $mValue){

	   return $this->aProperties[$sName] = $mValue;

	} // function setProperty()

	/**
	 * Checks whether property is set or not
	 *
	 * @param string $sName Property name
	 * @return bool True if property is set, false otherwise
	 */
	public function isSetProperty($sName){

		return isset($this->aProperties[$sName]);

	} // function isSetProperty()

	/**
	 * Unsets property
	 *
	 * @param string $sName Property name
	 * @return bool True if property existed and was unset, false otherwise
	 */
	public function unsetProperty($sName){

		if (isset($this->aProperties[$sName])){

			unset($this->aProperties[$sName]);

			return true;

		} else {

			return false;

		} // if

	} // function unsetProperty()

	/**
	 * Import array of properties
	 *
	 * If array key is string, it becomes name of the property. Otherwise, consecutive properties are
	 *
	 * @param array $aProperties Array of properties (pairs key=>value)
	 */
	public function importProperties($aProperties){

		$aCurrentKeys = array_fill_keys(array_keys($this->aProperties),true);

		foreach ((array)$aProperties as $mKey=>$mValue){

			if (is_string($mKey)){

                if (is_object($mValue)){

                    $this->setObject($mKey, $mValue);

                } else {

				    $this->setProperty($mKey,$mValue);

                } // if

				if (isset($aCurrentKeys[$mKey])){

					$aCurrentKeys[$mKey] = false;

				} // if

			} else {

				if (is_object($mValue)){

					$this->setObject($mValue);

				} else if ($sFreeKey = array_search(true, $aCurrentKeys)){

				    $this->setProperty($sFreeKey,$mValue);

				    $aCurrentKeys[$sFreeKey] = false;

				} // if

			} // if

		} // foreach

	} // function importProperties()

	/**
	 * Get all properties with names matching pattern
	 *
	 * @param string|null $sPattern PCRE Pattern
	 * @param bool $bRawPCR If false (default) change pattern from wildcards (*,?) to PCRE compliant
	 * @return array Matching properties (array of key=>value pairs) or all properties if $sPattern == null
	 */
	public function getProperties($sPattern = null, $bRawPCRE = false){

		if ($sPattern == null){

			return $this->aProperties;

		} else {

			if ($bRawPCRE !== true){

				$sPattern = '/^'.str_replace(array('\\','.','*','?'),array('\\\\','\.','.*','.{1}'),$sPattern).'$/iU';

			} // if

			return array_intersect_key($this->aProperties, array_fill_keys(preg_grep($sPattern, array_keys($this->aProperties)),null));

		} // if

	} // function getProperties()

		/**
	 * Get all objects with names matching pattern
	 *
	 * @param string|null $sPattern PCRE Pattern
	 * @param bool $bRawPCR If false (default) change pattern from wildcards (*,?) to PCRE compliant
	 * @return array Matching objects or all objects if $sPattern == null
	 */
	public function getObjects($sPattern = null, $bRawPCRE = false){

		if ($sPattern == null){

			return $this->aObjects;

		} else {

			if ($bRawPCRE !== true){

				$sPattern = '/^'.str_replace(array('\\','.','*','?'),array('\\\\','\.','.*','.{1}'),$sPattern).'$/iU';

			} // if

			return array_intersect_key($this->aObjects, array_fill_keys(preg_grep($sPattern, array_keys($this->aObjects)),null));

		} // if

	} // function getObjects()

	/**
	 * Calls method
	 *
	 * @param string $sName Method name
	 * @param mixed Arg1, mixed Arg2... Arguments for called function
	 * @return mixed|null Method return or null if failed
	 */
	public function call($sName){

        $aArgs = func_get_args();

        unset($aArgs[0]);

        if (method_exists($this, $sName)){

            return call_user_func_array(array($this,$sName), $aArgs);

        } else if (function_exists($sName)) {

            return call_user_func_array($sName, $aArgs);

        } // if

        foreach ($this->aObjects as $oObject){

			if (method_exists($oObject,$sName)){

				return call_user_func_array(array($oObject,$sName), $aArgs);

			} // if

		} // foreach

        return null;

	} // function call()

	/**
	 * Filters variable through given method
	 *
	 * @param string $sName Method name
	 * @param mixed $mValue Variable to be passed
	 * @return mixed Return value of method or $mValue if method not exists
	 */
	public function filter($sName, &$mValue){

		if (empty($sName) || !method_exists($this, $sName)){

			return $mValue;

		} else {

			return $this->call($sName, $mValue);

		} // if

	} // function filter()
/**
     * Gets context property or default if context property not set or property with given
     *
     * @param string $sPropertyName
     * @param string|null $sContext context or null (default then)
     * @return mixed Properety
     */
    public function getContextProperty($sPropertyName, $sContext = null, $sMyName = null){

    	if ($sMyName == null) {

    		$sMyName = $this->convertCCToName(get_class($this));

    	}

        if ($sContext == null) {

            $sContext = $this->getProperty($sMyName.'.context');

        } // if

        if ($this->isSetProperty($sMyName.'.'.$sContext.'.'.$sPropertyName)){

            return $this->getProperty($sMyName.'.'.$sContext.'.'.$sPropertyName);

        } else if ($this->isSetProperty($sMyName.'.default.'.$sPropertyName)) {

          return $this->getProperty($sMyName.'.default.'.$sPropertyName);

        } else {

          return $this->getProperty($sPropertyName);

        } // if

    } // function

	/**
	 * Converts string from camelCaps to lowerstr with dots (mySmallExample -> my.small.example)
	 *
	 * @param string $sString Sting to convert
	 * @return string converted string
	 *
	 */
	public function convertCCToName($sString){

		return strtolower(preg_replace('([A-Z]{1})', '.$0', strval($sString)));

	} // function

	public function __destruct(){

		foreach ($this->aObjects as $sKey=>&$oObject){

			if ($oObject instanceof yzCore){

			 $oObject->unsetObject($this);

			} // if

			unset($this->aObjects[$sKey]);

		} // foreach

	} // function

} // class yzCore

?>